////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////

#include "SCData.hpp"

#include "SCProgram.hpp"
#include "SComputationInfo.hpp"

#include <exception>
#include "Exception.hpp"
#include "IOFileException.hpp"
#include "OutOfBoundsException.hpp"

#include <sstream>

////////////////////////////////////////////////////////////////////////////////
// Namespaces
////////////////////////////////////////////////////////////////////////////////

namespace SC {

////////////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////////////

using namespace std;
using namespace Exceptions;
using namespace Toolkit;

/// Define a pointer on a function with no paremeter and no return value.
typedef void (*InitFuncPtr)(const string &);
typedef void (*CloseFuncPtr)(void);

SCData::SCData() {
	this->Clear();
}

SCData::~SCData() {
	this->Clear();
}

////////////////////////////////////////////////////////////////////////////////
// Public methods
////////////////////////////////////////////////////////////////////////////////

void SCData::Init(const SCProgram & prog, const vector<string> & plugins_dirs) {

	// Clear old data
	this->Clear();

	// Word length
	this->word_length = prog.GetWordLength();

	// Code map
	for (unsigned int i=0; i<prog.GetCodes().size(); ++i)
		this->code_map[prog.GetCodes()[i].GetFirst()] = prog.GetCodes()[i].GetSecond();

	// Offsets
	this->func_offset.Begin() = prog.GetFuncOffset().GetBegin();
	this->func_offset.End() = prog.GetFuncOffset().GetEnd();
	int func[2] = { this->func_offset.Begin(), this->func_offset.End()-this->func_offset.Begin()+1};

	// Systems initialisation
	for(unsigned int i=0; i<prog.GetSysInst().size(); ++i)
		this->systems.push_back(SCSystem(prog.GetWordLength()));
	for (unsigned int i=0; i<this->systems.size(); ++i) {
		// Create new system
		SCSystem & sys = this->systems[i];
		// Setup system data
		if (prog.GetSysInst()[i].GetFirst() >= prog.GetSysDecl().size())
			throw OutOfBoundsException("Init data error : Wrong system instance index - Binary file may be corrupted");
		string decl	= prog.GetSysDecl()[prog.GetSysInst()[i].GetFirst()].GetSecond();
		sys.ReplaceSchema1(decl.substr(0,prog.GetWordLength()));
		sys.ReplaceKernel(decl.substr(prog.GetWordLength(),prog.GetWordLength()));
		sys.ReplaceSchema2(decl.substr(2*prog.GetWordLength(),prog.GetWordLength()));
		// Check whether the system is a context
		for (unsigned int i=0; i<prog.GetFunctions().size(); ++i) {
			if (prog.GetFunctions()[i].GetSecond() != "NOP") {
				if (prog.GetFunctions()[i].GetFirst().substr(func[0],func[1]) == sys.GetKernel(func[0],func[1])) {
					this->contexts.push_back(&sys);
					break;
				}
			}
		}
	}

	// Scopes
	for (unsigned int i=0; i<this->systems.size(); ++i) {
		for (unsigned int j=0; j<prog.GetSysInst()[i].GetThird().size(); ++j) {
			if (prog.GetSysInst()[i].GetThird()[j] >= this->systems.size())
				throw OutOfBoundsException("Init data error : Wrong system instance index - Binary file may be corrupted");
			// Get system to grab
			SCSystem & sys = this->systems[prog.GetSysInst()[i].GetThird()[j]];
			// If not already in the scope
			if (!this->systems[i].HasSubSystem(sys)) {
				// Add to its super systems the currently processed system
				sys.addSuperSystem(this->systems[i]);
				// Add it to the currently processed system's sub systems 
				this->systems[i].addSubSystem(sys);
			}
		}
	}

	// If plugins directories are provided, load the plugins (required for execution)
	if (!plugins_dirs.empty()) {

		// GetFunctions modules
		this->plugins.resize(prog.GetFuncModules().size());
		for (unsigned int i=0; i<prog.GetFuncModules().size(); ++i) {
			unsigned int p = 0;
			while (p < plugins_dirs.size()) {
				try {
					this->plugins[i].Attach(plugins_dirs[p]+"/"+prog.GetFuncModules()[i]);
					break;
				}
				catch (exception &) { ++p; }
			}
			if (p == plugins_dirs.size())
				throw IOFileException("Cannot find or open library \"" + prog.GetFuncModules()[i] + "\" in any of the plugins directories");
			try {
				FuncPtr init_plugin = this->plugins[i].GetFunction("InitPlugin");
				if (init_plugin != NULL) (*((InitFuncPtr)init_plugin))(prog.GetName());
			}
			catch (exception & e) {
				throw IOFileException("Error initialising plugin \"" + this->plugins[i].GetFileName() + "\" : \n\t" + e.what());
			}
		}
	
		// GetFunctions
		for (unsigned int i=0; i<prog.GetFunctions().size(); ++i) {
			if (prog.GetFunctions()[i].GetSecond() != "NOP") {
				FuncPtr f_ptr = NULL;
				unsigned int p_idx = 0;
				while ((f_ptr == NULL) && (p_idx < this->plugins.size()))
					f_ptr = this->plugins[p_idx++].GetFunction(prog.GetFunctions()[i].GetSecond());
				if (f_ptr != NULL)
					this->func_map[prog.GetFunctions()[i].GetFirst().substr(func[0],func[1])] = SCFuncPtrWrapper(prog.GetFunctions()[i].GetSecond(),(SCFuncPtr)(f_ptr));
				else
					throw IOFileException("Cannot find \"" + prog.GetFunctions()[i].GetSecond() + "\" in any plugin");
			}
		}
		
	}

}

SCSystem & SCData::Context(const unsigned int i) {
	if (i >= this->contexts.size()) {
		stringstream ss; ss << "Context system reference required at " << i << " when number of contexts is " << this->contexts.size();
		throw OutOfBoundsException(ss.str());
	}
	return *this->contexts[i];
}

const bool SCData::AddContext(SCSystem & sys) {
	if (find(this->contexts.begin(),this->contexts.end(),&sys) == this->contexts.end()) {
		this->contexts.push_back(&sys);
		return true;
	}
	return false;
}

void SCData::RemoveContext(SCSystem & sys) {
	this->contexts.erase(find(this->contexts.begin(),this->contexts.end(),&sys));
}

const SCSystem & SCData::GetSystem(const unsigned int i) const {
	if (i >= this->systems.size()) {
		stringstream ss; ss << "System reference required at " << i << " when number of systems is " << this->systems.size();
		throw OutOfBoundsException(ss.str());
	}
	return this->systems[i];
}

SCSystem & SCData::System(const unsigned int i) {
	if (i >= this->systems.size()) {
		stringstream ss; ss << "System reference required at " << i << " when number of systems is " << this->systems.size();
		throw OutOfBoundsException(ss.str());
	}
	return this->systems[i];
}

const SCSystem & SCData::GetUniverse() const {
	for (unsigned int i=0; i<this->systems.size(); ++i)
		if (this->systems[i].NbSuperSystems() == 0)
			return this->systems[i];
	throw Exception("Incorrect system data : No universe");
}

SCSystem & SCData::Universe() {
	for (unsigned int i=0; i<this->systems.size(); ++i)
		if (this->systems[i].NbSuperSystems() == 0)
			return this->systems[i];
	throw Exception("Incorrect system data : No universe");
}

void SCData::Clear() {
	// Set word length to 0
	this->word_length = 0;
	// Empty lists
	this->systems.clear();
	this->contexts.clear();
	this->code_map.clear();
	this->func_map.clear();
	// Close plugins
	for (unsigned int i=0; i<this->plugins.size(); ++i) {
		try {
			FuncPtr close_plugin = this->plugins[i].GetFunction("ClosePlugin");
			if (close_plugin != NULL) (*((CloseFuncPtr)close_plugin))();
		}
		catch (exception & e) {
			cerr << "Error closing plugin " << this->plugins[i].GetFileName() << " : " << e.what() << endl;
		}
	}
	this->plugins.clear();
	// Reset func offset
	this->func_offset.Begin() = this->func_offset.End() = -1;
}

void SCData::ApplyComputation(const SComputationInfo & info) {
	// Apply changes
	this->apply_changes(this->systems[info.sys1_idx], info.sys1_changes);
	this->apply_changes(this->systems[info.ctx_idx], info.ctx_changes);
	this->apply_changes(this->systems[info.sys2_idx], info.sys2_changes);
}

////////////////////////////////////////////////////////////////////////////////
// Protected methods
////////////////////////////////////////////////////////////////////////////////

void SCData::apply_changes(SCSystem & sys, const SCSystemChanges & sys_changes) {
	for (unsigned int i=0; i<sys_changes.s1_changes.size(); ++i)
		sys.ReplaceSchema1(sys_changes.s1_changes[i].second, sys_changes.s1_changes[i].first, sys_changes.s1_changes[i].second.size());
	for (unsigned int i=0; i<sys_changes.k_changes.size(); ++i)
		sys.ReplaceKernel(sys_changes.k_changes[i].second, sys_changes.k_changes[i].first, sys_changes.k_changes[i].second.size());
	for (unsigned int i=0; i<sys_changes.s2_changes.size(); ++i)
		sys.ReplaceSchema2(sys_changes.s2_changes[i].second, sys_changes.s2_changes[i].first, sys_changes.s2_changes[i].second.size());
	for (unsigned int i=0; i<sys_changes.super_changes.size(); ++i) {
		// Gaining a super system
		if (sys_changes.super_changes[i].first) this->systems[sys_changes.super_changes[i].second].Grab(sys);
		// Loosing a super system
		else this->systems[sys_changes.super_changes[i].second].Release(sys);
	}
	for (unsigned int i=0; i<sys_changes.sub_changes.size(); ++i) {
		// Gaining a sub system
		if (sys_changes.sub_changes[i].first) sys.Grab(this->systems[sys_changes.sub_changes[i].second]);
		// Loosing a sub system
		else sys.Release(this->systems[sys_changes.sub_changes[i].second]);
	}
}

////////////////////////////////////////////////////////////////////////////////
// Closing Namespaces
////////////////////////////////////////////////////////////////////////////////

}

////////////////////////////////////////////////////////////////////////////////
